package cn.buk.tms.dao;

import cn.buk.common.sc.CommonSearchCriteria;
import cn.buk.common.sc.Page;
import cn.buk.tms.factory.OperationLogFactory;
import cn.buk.tms.common.constant.TmsOrderConstant;
import cn.buk.common.util.DateUtil;
import cn.buk.tms.entity.misc.*;
import cn.buk.tms.sc.SearchFlightOrderCriteria;
import jakarta.persistence.criteria.*;
import org.springframework.stereotype.Component;

import jakarta.transaction.Transactional;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import static cn.buk.tms.common.constant.TmsOrderConstant.*;

@Component
@Transactional
public class AttractionDaoImpl extends AbstractDao implements AttractionDao {

  @Override
  public int saveAttractionInfo(final int userId, AttractionInfo o) {
    final boolean isNew = o.getId() == 0;
    if (isNew) {
      em.persist(o);
    } else {
      o.setLastUpdate(DateUtil.getCurDateTime());
      em.merge(o);
    }

    //记录日志
    User user = em.find(User.class, userId);
    OperationLog log = OperationLog.createLog(o.getEnterpriseId(), OPERATING_OBJECT_BASIC_DATA);
    log.setOperator(user.getUsername());
    if (isNew) {
      log.setContent("新建信息：" + o.getName());
    } else {
      log.setContent("修改景点(" + o.getName() + ")信息.");
    }

    return o.getId();
  }

  @Override
  public AttractionInfo getAttractionInfoById(int id) {
    return em.find(AttractionInfo.class, id);
  }

  private List<Predicate> buildPredicates_searchAttractions(CriteriaBuilder cb, Root<AttractionInfo> root,
                                                            int enterpriseId, CommonSearchCriteria sc) {
    List<Predicate> predicates = new ArrayList<>();
    Predicate predicate = cb.conjunction();
    predicates.add(predicate);

    predicate = cb.equal(root.get(AttractionInfo_.enterpriseId), enterpriseId);
    predicates.add(predicate);

    return predicates;
  }

  @Override
  public List<AttractionInfo> searchAttractions(final int userId, CommonSearchCriteria sc) {
    User user = em.find(User.class, userId);
    final int enterpriseId = user.getEnterpriseId();

    List<AttractionInfo> results = null;

    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<AttractionInfo> cq = cb.createQuery(AttractionInfo.class);
    Root<AttractionInfo> root = cq.from(AttractionInfo.class);
    root.alias("o");

    var predicates0 = buildPredicates_searchAttractions(cb, root, enterpriseId, sc);

    CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
    Root<AttractionInfo> countRoot = countQuery.from(AttractionInfo.class);
    countRoot.alias("o");

    var predicates1 = buildPredicates_searchAttractions(cb, countRoot, enterpriseId, sc);

    countQuery.select(cb.count(countRoot)).where(predicates1.toArray(new Predicate[predicates1.size()]));
    Long count = em.createQuery(countQuery).getSingleResult();

    if (count.intValue() > 0) {
      cq.where(predicates0.toArray(new Predicate[predicates0.size()]));

      Page page = sc.getPage();
      page.setRowCount(count.intValue());
      int i = (page.getPageNo() - 1) * page.getPageSize();

      results = em.createQuery(cq)
              .setFirstResult(i)
              .setMaxResults(page.getPageSize()).getResultList();
    }

    return results == null ? new ArrayList<>() : results;
  }

  @Override
  public int saveAttractionPrice(int userId, int attractionId, Date startDate, Date endDate, int price, String remark) {
    if (startDate.getTime() > endDate.getTime()) {
      throw new RuntimeException("Start date can not be greater than end date.");
    }
    User user = em.find(User.class, userId);
    AttractionInfo attractionInfo = em.find(AttractionInfo.class, attractionId);

    int count = 0;
    while (true) {
      final Date priceDate = DateUtil.addDays(startDate, count);
      if (priceDate.getTime() > endDate.getTime()) {
        break;
      }

      count++;

      AttractionPrice priceInfo = new AttractionPrice();
      priceInfo.setEnterpriseId(user.getEnterpriseId());
      priceInfo.setAttractionInfo(attractionInfo);
      priceInfo.setEffectDate(priceDate);
      priceInfo.setPrice(price);
      priceInfo.setRemark(remark);

      em.persist(priceInfo);
    }

    return count;
  }

  private List<Predicate> buildPredicates_searchAttractionPrices(CriteriaBuilder cb, Root<AttractionPrice> root,
                                                            int enterpriseId, int attractionId, CommonSearchCriteria sc) {
    List<Predicate> predicates = new ArrayList<>();
    Predicate predicate = cb.conjunction();
    predicates.add(predicate);

    predicate = cb.equal(root.get(AttractionPrice_.enterpriseId), enterpriseId);
    predicates.add(predicate);

    predicate = cb.equal(root.get(AttractionPrice_.attractionInfo), attractionId);
    predicates.add(predicate);

    return predicates;
  }

  @Override
  public List<AttractionPrice> searchAttractionPrices(int userId, int attractionId, CommonSearchCriteria sc) {
    User user = em.find(User.class, userId);
    final int enterpriseId = user.getEnterpriseId();

    List<AttractionPrice> results = null;

    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<AttractionPrice> cq = cb.createQuery(AttractionPrice.class);
    Root<AttractionPrice> root = cq.from(AttractionPrice.class);
    root.alias("o");

    var predicates0 = buildPredicates_searchAttractionPrices(cb, root, enterpriseId, attractionId, sc);


    //计算根据条件查询得出的数据总数
    CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
    Root<AttractionPrice> countRoot = countQuery.from(AttractionPrice.class);
    countRoot.alias("o");

    var predicates1 = buildPredicates_searchAttractionPrices(cb, countRoot, enterpriseId, attractionId, sc);

    countQuery.select(cb.count(countRoot)).where(predicates1.toArray(new Predicate[predicates1.size()]));
    Long count = em.createQuery(countQuery).getSingleResult();

    if (count.intValue() > 0) {
      cq.where(predicates0.toArray(new Predicate[predicates0.size()]));
      List<Order> orderByes = new ArrayList<>();
      orderByes.add(cb.asc(root.get("effectDate")));
      cq.orderBy(orderByes);

      Page page = sc.getPage();
      page.setRowCount(count.intValue());
      int i = (page.getPageNo() - 1) * page.getPageSize();

      results = em.createQuery(cq)
              .setFirstResult(i)
              .setMaxResults(page.getPageSize()).getResultList();
    }

    return results == null ? new ArrayList<>() : results;
  }

  @Override
  public int deleteAttractionPrice(int userId, int attractionId, int priceId) {
    return em.createQuery("delete from AttractionPrice o " +
            "where o.id = :id " +
            "and o.attractionInfo.id = :attractionId")
            .setParameter("id", priceId)
            .setParameter("attractionId", attractionId)
            .executeUpdate();
  }

  private List<Predicate> buildPredicates_searchTicketOrders(CriteriaBuilder cb, Root<TicketOrder> root,
                                                             int enterpriseId, SearchFlightOrderCriteria sc) {
    List<Predicate> predicates = new ArrayList<>();

    Predicate predicate = cb.conjunction();
    predicates.add(predicate);

    predicate = cb.equal(root.get(TicketOrder_.enterpriseId), enterpriseId);
    predicates.add(predicate);


    if (sc.getOrderNo().length() > 0) {
      predicate = cb.equal(root.get(TicketOrder_.orderNo), sc.getOrderNo());
      predicates.add(predicate);
    }

    if (sc.getStatus() > -1) {
      predicate = cb.equal(root.get(TicketOrder_.status), sc.getStatus());
      predicates.add(predicate);
    } else if (sc.getOrderNo().length() == 0) {
      //默认不显示已取消订单
      predicate = cb.notEqual(root.get(TicketOrder_.status), TICKET_ORDER_STATUS_CANCELED);
      predicates.add(predicate);
    }

    if (sc.getBeginDate() != null) {
      predicate = cb.greaterThanOrEqualTo(root.get(TicketOrder_.travelDate), sc.getBeginDate());
      predicates.add(predicate);
    }

    if (sc.getEndDate() != null) {
      predicate = cb.lessThanOrEqualTo(root.get(TicketOrder_.travelDate), sc.getEndDate());
      predicates.add(predicate);
    }

    return predicates;
  }

  @Override
  public List<TicketOrder> searchTicketOrders(final int enterpriseId, SearchFlightOrderCriteria sc) {
    List<TicketOrder> results = null;

    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<TicketOrder> cq = cb.createQuery(TicketOrder.class);
    Root<TicketOrder> root = cq.from(TicketOrder.class);
    root.alias("o");

    var predicates0 = buildPredicates_searchTicketOrders(cb, root, enterpriseId, sc);


    //计算根据条件查询得出的数据总数
    CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
    Root<TicketOrder> countRoot = countQuery.from(TicketOrder.class);
    countRoot.alias("o");

    var predicates1 = buildPredicates_searchTicketOrders(cb, countRoot, enterpriseId, sc);

    countQuery.select(cb.count(countRoot))
            .where(predicates1.toArray(new Predicate[predicates1.size()]));
    Long count = em.createQuery(countQuery).getSingleResult();

    int maxResults = count.intValue();
    if (maxResults > 0) {
      cq.where(predicates0.toArray(new Predicate[predicates0.size()]));
      List<Order> orderByes = new ArrayList<>();
      orderByes.add(cb.desc(root.get("id")));

      cq.orderBy(orderByes);

      Page page = sc.getPage();
      page.setRowCount(maxResults);
      int i = (page.getPageNo() - 1) * page.getPageSize();

      if (i < 0) {
        i = 0;
      }

      results = em.createQuery(cq)
              .setFirstResult(i)
              .setMaxResults(page.getPageSize()).getResultList();
    }

    if (results == null) {
      results = new ArrayList<>();
    }

    return results;
  }

  @Override
  public TicketOrder getTicketOrderById(int orderId) {
    return em.find(TicketOrder.class, orderId);
  }

  @Override
  public List<OperationLog> searchTicketOrderHistory(int enterpriseId, int orderId) {
    return searchOrderHistory(enterpriseId, OPERATING_OBJECT_TICKET_ORDER, orderId);
  }

  @Override
  public int cancelTicketOrder(final int enterpriseId, final String username, final int orderId, String remark) {
    TicketOrder ticketOrder = em.find(TicketOrder.class, orderId);
    em.refresh(ticketOrder);
    if (ticketOrder.getEnterpriseId() != enterpriseId) {
      throw new RuntimeException("Ticket order is not existed.");
    }

    //TODO 如果订单已付款，则需要把金额退还到客户的账户中
    if (ticketOrder.getPayStatus() == PAY_STATUS_PAID) {
      final int customerEnterpriseId = ticketOrder.getCustomer().getCustomerEnterpriseId();
      EnterpriseBalance entBalance = doGetEnterpriseBalance(enterpriseId, customerEnterpriseId, em);

      final int fee = (int)(ticketOrder.getTotal() * 100);

        AmountHistory amountHistory = new AmountHistory();
        amountHistory.setOwnerId(enterpriseId);
        amountHistory.setEnterpriseId(customerEnterpriseId);
        amountHistory.setOrderType(ORDER_TYPE_TICKET);
        amountHistory.setOrderId(orderId);
        amountHistory.setDebt(fee);
        amountHistory.setRemark("门票订单(" + ticketOrder.getOrderNo() + ")取消退款。");

        entBalance.incCashBalance(fee);
        amountHistory.setAccountBalance(entBalance.getCashBalance());

        em.merge(entBalance);
        em.persist(amountHistory);
    }

    int retCode = em.createQuery("update TicketOrder o set " +
            "o.status = :newStatus, o.lastUpdate = :lastUpdate " +
            "where o.id = :id " +
            "and o.enterpriseId = :enterpriseId " +
            "and o.status in (:oldStatus1, :oldStatus2, :oldStatus3) ")
            .setParameter("id", orderId)
            .setParameter("enterpriseId", enterpriseId)
            .setParameter("oldStatus1", TICKET_ORDER_STATUS_NOT_SUBMITTED)
            .setParameter("oldStatus2", TICKET_ORDER_STATUS_WAITING)
            .setParameter("oldStatus3", TICKET_ORDER_STATUS_PROCESSING)
            .setParameter("newStatus", TICKET_ORDER_STATUS_CANCELED)
            .setParameter("lastUpdate", DateUtil.getCurDateTime())
            .executeUpdate();
    if (retCode != 1) {
      throw new RuntimeException("To cancel ticket order failed.");
    }

    OperationLog log = OperationLog.createLog(enterpriseId, OPERATING_OBJECT_TICKET_ORDER);
    log.setOperator(username);
    log.setOrderId(orderId);
    log.setContent("取消订单: " + remark);
    em.persist(log);

    return retCode;
  }

  @Override
  public int processTicketOrder(int enterpriseId, String username, int orderId) {
    int retCode = em.createQuery("update TicketOrder o " +
            "set o.status = :newStatus, " +
            "o.ticketer = :ticketer, " +
            "o.lastUpdate = :lastUpdate " +
            "where o.id = :id and o.enterpriseId = :enterpriseId and o.status = :oldStatus")
            .setParameter("id", orderId)
            .setParameter("enterpriseId", enterpriseId)
            .setParameter("oldStatus", TmsOrderConstant.TICKET_ORDER_STATUS_WAITING)
            .setParameter("newStatus", TmsOrderConstant.TICKET_ORDER_STATUS_PROCESSING)
            .setParameter("ticketer", username)
            .setParameter("lastUpdate", DateUtil.getCurDateTime())
            .executeUpdate();
    if (retCode != 1) {
      throw new RuntimeException("update status failed.");
    }

    OperationLog log = OperationLogFactory.createOperationLog(enterpriseId, OPERATING_OBJECT_TICKET_ORDER);
    log.setOrderId(orderId);
    log.setOperator(username);

    String content = "处理订单";
    log.setContent(content);
    em.persist(log);

    return retCode;
  }

  @Override
  public int finishTicketOrder(int enterpriseId, String username, int orderId, String remark) {
    int retCode = em.createQuery("update TicketOrder o " +
            "set o.status = :newStatus, " +
            "o.lastUpdate = :lastUpdate " +
            "where o.id = :id and o.enterpriseId = :enterpriseId and o.status = :oldStatus")
            .setParameter("id", orderId)
            .setParameter("enterpriseId", enterpriseId)
            .setParameter("oldStatus", TmsOrderConstant.TICKET_ORDER_STATUS_PROCESSING)
            .setParameter("newStatus", TmsOrderConstant.TICKET_ORDER_STATUS_DONE)
            .setParameter("lastUpdate", DateUtil.getCurDateTime())
            .executeUpdate();
    if (retCode != 1) {
      throw new RuntimeException("update status failed.");
    }

    OperationLog log = OperationLogFactory.createOperationLog(enterpriseId, OPERATING_OBJECT_TICKET_ORDER);
    log.setOrderId(orderId);
    log.setOperator(username);

    String content = "订单处理完毕(" + remark + ")";
    log.setContent(content);
    em.persist(log);

    return retCode;
  }
}
